package p2p.peer;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Socket;
import java.util.List;
import java.util.Random;
import java.util.UUID;

import p2p.logic.connection.Command;
import p2p.logic.connection.Connection;
import p2p.logic.connection.ConnectionOwner;
import p2p.logic.connection.client.Client;
import p2p.logic.connection.communication.AbstractCommunicationHandler;
import p2p.logic.connection.communication.gateway.Gateway;
import p2p.logic.connection.communication.message.Message;
import p2p.logic.connection.communication.type.Send;
import p2p.logic.log.Logger;
import p2p.logic.util.Utils;
import p2p.peer.command.BroadCast;
import p2p.peer.command.Connect;
import p2p.peer.command.Leave;
import p2p.peer.command.Spawn;
import p2p.peer.command.visitor.GSVisitor;
import p2p.simulation.PeerManager;

public class Peer implements ConnectionOwner, Runnable{
	
	private boolean isRunning = false;
	private Gateway gateway;
	private int id;
	private AbstractCommunicationHandler handler;
	private ClientManager manager;
	
	public static volatile int count = 0;
	
	public Peer(int port){
		
		this.init(port);

	}
	
	
	public Peer(){
		
		
	}
	
	private void init(int port){
		
		Peer.count++;
		
		this.id = count;
		this.manager = new ClientManager(this);
		this.gateway = new LocalGateway("127.0.0.1", port, this);
		this.handler = new CommunicationHandler(this);
			
		Logger.setILogger(new StandardLogger());
		Logger.setIsDebug(true);
	}

	
	public void listen(){
				
		isRunning = true;
		
		gateway.listen(this.gateway.getPort());
		
	}
	
	public void broadCast(){
		
		Logger.consoleLog(this.getId() + " is sending Broadcast");

		Gateway g = this.getGateway();
		
		UUID uuid = Utils.newUUID();

		for (Client c : manager.getClients()){
			
			BroadCast cast = new BroadCast();
			
			Message m = new Message(g.getIp(), g.getPort(), c.getIp(), c.getPort(), cast);
			
			m.setUuid(uuid);
			
			g.send(c, m);
		}
		
		
	}
	
	public void broadCast(UUID uuid){
		
		Logger.consoleLog(this.getId() + " is sending Broadcast");
		
		Gateway g = this.getGateway();
		
		for (Client c : manager.getClients()){
			
			BroadCast cast = new BroadCast();
			
			Message m = new Message(g.getIp(), g.getPort(), c.getIp(), c.getPort(), cast);
			
			m.setUuid(uuid);
			
			g.send(c, m);
		}
		
		
	}
	public void connect(String ip, int port){
		
		Message message = new Message(this, ip, port, new Connect());
		
		Connection c = new Connection(new Send(this.handler, message), false);
		
		new Thread(c, "connect").start();
		
	}
	
	public void spawn(Client client){
		
		Message m = new Message(this.getGateway().getIp(), this.getGateway().getPort(), client.getIp(), client.getPort(), new Spawn(true));

		gateway.send(client, m);

	}
	
	public void leave(){
		
		List<Client> clientList = this.manager.getClients();
		
		for (int i = 0; i < this.manager.getClients().size(); i++){
			
			Client current = clientList.get(i);
			Client next;
			
			if (i + 1 < this.manager.getClients().size())
				next = clientList.get(i+1);
			else
				next = clientList.get(0);
			
			Message m = new Message(this.getGateway().getIp(), this.getGateway().getPort(), current.getIp(), current.getPort(), new Leave());
			
			m.addClientToPayload(next);
			
			gateway.send(current, m);
			
		}
		
	}
	
	public void stop(){
		
		this.isRunning = false;
		
		this.gateway.stop();
		
	}

	@Override
	public boolean isRunning() {
		
		return this.isRunning;
	
	}

	@Override
	public Gateway getGateway() {
		
		return gateway;
	
	}

	@Override
	public void run() {
	
		this.listen();
	
	}

	@Override
	public String getId() {
		//		return "Peer (" + this.count + ":" + this.getGateway().getPort() + ")";

		return "Peer (" + id + ":" + this.gateway.getPort() + ")";
	
	}

	@Override
	public AbstractCommunicationHandler getCommunicationHandler() {
		return this.handler;
	}

	@Override
	public void recievedAck(Message message) {
		
		Logger.debug(this.getId() + " recieved for ack " + message.getUuid().toString());

		Command c = new Command(new GSVisitor(this.manager, message, true));
		if (message.getCommand() == 1)
			PeerManager.getInstance().updateGraph(this);

		c.execute();
		
	}

	@Override
	public void recievedMessage(Message message) {
		
		Command c = new Command(new GSVisitor(this.manager, message, false));
		
		if (message.isBroadCast() && !this.handler.hasAlreadySendBroadCast(message.getUuid())){

			Logger.consoleLog(this.getId() + " Recieved broadcast: " + message.getSenderPort());

			this.broadCast(message.getUuid());
		
		} 
		
		c.execute();
		
		if (message.getCommand() == 1){
			PeerManager.getInstance().updateGraph(this);
		}
		
	}
	
	public List<Client> getNeighbors(){
		return this.manager.getClients();
	}
	
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "" + id + ":" + this.getGateway().getPort();
	}
	
	public Client getPeerAsClient(){
		return new Client(this.gateway.getIp(), this.gateway.getPort());
	}
	
}
